p3d = local_import('p3d')
from math import *
from gluon.admin import *
import sys, os, re
import inspect

sys.path.insert(0, os.path.join(request.folder, 'private/development'))
from Converters import universal_converter

manager = True

def get(table):
    try:
        record = table[int(request.args(0))]
    except:
        record = None       
    if not record:
        redirect(URL(r=request,f='error'))
    return record

def detect_fileformat(uploaded_data):
    for format in [universal_converter.MilcField,
                   universal_converter.QioField,
                   universal_converter.ILDGField,
                   universal_converter.Nersc3x3Field,
                   universal_converter.Nersc3x2Field]:
        uploaded_data.seek(0)
        f = format(uploaded_data)
        found_format = False
        try:
            f.get_header()
            file_format = format.__name__
            found_format = True
        except Exception, e:
            # handle NERSC files with incomplete headers
            uploaded_data.seek(0)
            header = uploaded_data.read(10000)
            if re.compile('DATATYPE = 4D_SU3_GAUGUE').search(header):
                file_format = 'Nersc3x2Field'
                f.precision = 'f'
            elif re.compile('DATATYPE = 4D_SU3_GAUGE_3x3').search(header):
                file_format = 'Nersc3x3Field'
                f.precision = 'f'
            else:
                print format.__name__, str(e)
    return_value = None
    lattice_size = 'x'.join(map(lambda x: x.__str__(), f.lattice_size))
    endianess ='big' if f.endianess == '>' else 'little'
    precision = 'single' if f.precision == 'f' else 'double'
    uploaded_data.seek(0)
    if found_format:
        return_value = {'lattice_size':lattice_size,'endianess':endianess,'precision':precision,'file_format':file_format}
    return return_value

def __fixup():
    c=0    
    for item in db(db.configfile.id>0).select():
        file_details = detect_fileformat(open(locate_file(item.file),'rb'))
        if file_details:
            item.update_record(file_format=file_details['file_format'], lattice_size=file_details['lattice_size'],
                               endianness=file_details['endianess'], precision=file_details['precision'],
                               status=COMPLETED)
        else:
            item.update_record(status=FAILED)        
    return c

def error():
    return dict()

def index():
    try:
        request.args[:]=[]
        request.args.append(db(db.processedfile.id>0).select().first().id)
        return visual()
    except:
        redirect(URL(r=request,f='streams'))

def streams():
    return dict(form=crud.create(db.stream) if auth.user else None)

def fixfilename(form):
    form.vars.filename=request.vars.file.filename.replace('\\','/').split('/')[-1]

import glob
def folder(lyocation):
    location+='/*'
    files=glob.glob(location)
    for file in files:
        if file.endswith('.mdp'):
            pass
        else:
            filename=file.split('/')[-1]
            jobid=db.configfile.insert(file=file,local=True,filename=filename)
            submit_job('plaquette.py',jobid)
        

def stream():
    stream=get(db.stream)
    if auth.user and (stream.posted_by==auth.user.id or manager):
        deleted=False
        for item in request.vars:
            if item[:7] == 'delete_':
                del db.configfile[item[7:]]
                deleted=True
        if deleted: redirect(URL(r=request,args=request.args))
    db.configfile.stream.default=stream.id
    db.localupload.stream.default=stream.id
    if auth.user and (stream.posted_by==auth.user.id or manager):
        form=crud.create(db.configfile,onvalidation=fixfilename, onaccept=lambda form:submit_job('plaquette.py',form.vars.id))
        form2=crud.create(db.localupload,onaccept=lambda form2:folder(form2.vars.local_folder))
    else:
        form = None
        form2 = None
    token = db(db.uploadtoken.stream==stream.id).select().first()
    if not token:
        db.uploadtoken.insert(stream=stream,token=uuid.uuid4().hex, expiration_date=datetime.date.today() + datetime.timedelta(days=7))
        db.commit()
        token = db(db.uploadtoken.stream==stream.id).select().first()
    #del db.localupload[9]
    configfile = db(db.configfile.stream==stream.id).select().first()
    folders=db(db.localupload.id>0).select()
    return dict(stream=stream,form=form,form2=form2,token=token,configfile=configfile,folders=folders)

@auth.requires_login()
def renew_token():
    stream = get(db.stream)
    token = db(db.uploadtoken.stream == request.args(0)).select(orderby=~db.uploadtoken.expiration_date).first()
    if auth.user and (stream.posted_by == auth.user.id or manager):
        token.update_record(expiration_date = datetime.date.today() + datetime.timedelta(days=7))
    session.flash = 'Renewed upload token for stream "%s"' % stream.name
    redirect(URL(r=request,f='stream',args=stream.id))

@auth.requires_login()
def edit_stream():
    stream = get(db.stream)
    if stream.posted_by!=auth.user.id and not manager:
        session.flash='not authorized'
        redirect(URL(r=request,f='streams'))
    form=crud.update(db.stream,stream,next=URL(r=request,f='streams'))
    form[0][-1][1].append(TAG.button('cancel',_onclick='document.location="%s";return false' % URL(r=request,f='stream',args=stream.id)))
    return dict(form=form)

def process():
    configfile = get(db.configfile)
    if auth.user and (configfile.posted_by==auth.user.id or manager):
        deleted=False
        for item in request.vars:
            if item[:7] == 'delete_':
                del db.processedfile[item[7:]]
                deleted=True
        if deleted: redirect(URL(r=request,args=request.args))
    db.processedfile.configfile.default=configfile.id
    if auth.user and (configfile.posted_by==auth.user.id or manager):       
        form = crud.create(db.processedfile,
                           onaccept=lambda form: submit_job('algorithm.py',form.vars.id))
    else:
        form = None
    return dict(configfile=configfile,form=form)

@auth.requires_login()
def edit_configfile():
    configfile = get(db.configfile)
    if configfile.posted_by!=auth.user.id and not manager:
        session.flash='not authorized'
        redirect(URL(r=request,f='streams'))
    db.configfile.status.writable=True    
    form=crud.update(db.configfile,configfile,next=URL(r=request,f='streams'))
    form[0][-1][1].append(TAG.button('cancel',_onclick='document.location="%s";return false' % URL(r=request,f='process',args=configfile.id)))
    return dict(form=form)
    
def Dreduce(filename):
    import struct, re
    data = open(filename,'rb').read()
    i = data.find('LOOKUP_TABLE default\n')
    data2 = data[i+len('LOOKUP_TABLE default\n'):]
    k = len(data2)/4
    v = struct.unpack('>%sf' %k,data2)
    match = re.compile('DIMENSIONS\s+(?P<nx>\d*)\s+(?P<ny>\d*)\s+(?P<nz>\d*)',re.MULTILINE).search(data) 
    nx, ny, nz = int(match.group('nx')), int(match.group('ny')), int(match.group('nz'))
    #print nx,ny,nz
    if min(nx,ny,nz)>=16:
        nx_new=nx/2
        ny_new=ny/2
        nz_new=nz/2 
        points = [[[0.0 for k in range(nx_new)] for i in range(ny_new)] for m in range(nz_new)] 
        for z in range(nz_new):
            for y in range(ny_new):
                for x in range(nx_new):
                    points[x][y][z] = v[2*x+ny*(2*y+nz*2*z)]
    else:
        points = [[[0.0 for k in range(nx)] for i in range(ny)] for m in range(nz)] 
        for z in range(0,nz):
            for y in range(0,ny):
                for x in range(0,nx):
                    points[x][y][z] = v[int(x+nx*(y+ny*z))]
    return points

def visual():
    processedfile = get(db.processedfile)
    if auth.user and (processedfile.posted_by==auth.user.id or manager):
        deleted=False
        for item in request.vars:
            if item[:7] == 'delete_':
                del db.visualizedfile[item[7:]]
                deleted=True
        if deleted: redirect(URL(r=request,args=request.args))  
    db.visualizedfile.processedfile.default=processedfile.id
    obj=p3d.P3D(400,onrotate=URL(r=request,f='onrotate'))
    if not processedfile.vtkfile:
        session.flash='processing not completed'
        redirect(URL(r=request,f='process',args=processedfile.configfile))
    field = Dreduce(os.path.join(request.folder,'uploads',processedfile.vtkfile))
    obj.isosurface(field,0.3,0.0,red=255,green=0,blue=0)
    obj.isosurface(field,0.0,0.3,red=0,green=0,blue=255)
    if auth.user and (processedfile.posted_by==auth.user.id or manager):
        db.visualizedfile.name.default=processedfile.algorithm + '.'+\
            request.now.isoformat()
        form = crud.create(db.visualizedfile,
                           onaccept=lambda form:submit_job('visualize.py',form.vars.id))
    else:
        form = None
    return dict(form=form,obj=obj,processedfile=processedfile)

def visual_show():
    visualizedfile=get(db.visualizedfile)
    return dict(visualizedfile=visualizedfile)

def task():
    len_configfile=db(db.configfile.id>0).count()
    len_stream=db(db.stream.id>0).count()
    processedfile=db(db.processedfile.id>0).select()
    return dict(processedfile=processedfile,len_configfile=len_configfile,len_stream=len_stream)

def onrotate():
    session.p3d_alpha = request.vars.alpha
    session.p3d_beta = request.vars.beta
    return 0

@auth.requires_login()
def edit_processedfile():    
    processedfile=get(db.processedfile)
    if processedfile.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='process',args=processedfile.configfile))
    db.processedfile.status.writable=True    
    form=crud.update(db.processedfile,processedfile)
    form[0][-1][1].append(TAG.button('cancel',_onclick='document.location="%s";return false' % URL(r=request,f='visual',args=processedfile.id)))
    return dict(form=form)

@auth.requires_login()
def edit_visualizedfile():
    visualizedfile = get(db.visualizedfile)
    if visualizedfile.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='visual',args=visualizedfile.processedfile))
    db.visualizedfile.status.writable=True    
    form=crud.update(db.visualizedfile,visualizedfile)
    form[0][-1][1].append(TAG.button('cancel',_onclick='document.location="%s";return false' % URL(r=request,f='visual',args=visualizedfile.processedfile.id)))
    return dict(form=form)

@auth.requires_login()
def delete_stream():
    stream=get(db.stream)
    if stream.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='streams'))
    del db.stream[id]
    redirect(URL(r=request,f='streams'))

@auth.requires_login()
def delete_configfile():
    configfile=get(db.configfile)
    stream_id=configfile.stream
    if configfile.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='stream',args=stream_id))
    del db.configfile[configfile.id]
    redirect(URL(r=request,f='stream',args=stream_id))

@auth.requires_login()
def delete_processing():
    processedfile=get(db.processedfile)
    configfile_id=processedfile.configfile
    if processedfile.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='process',args=configfile_id))
    del db.processedfile[processedfile.id]
    redirect(URL(r=request,f='process',args=configfile_id))

@auth.requires_login()
def delete_visualizing():
    visualizedfile=get(db.visualizedfile)
    processedfile_id=visualizedfile.processedfile
    #processedfile=db.processedfile[processedfile_id]
    #configfile_id=processedfile.configfile
    if visualizedfile.posted_by!=auth.user.id:
        session.flash='Not authorized'
        redirect(URL(r=request,f='visual',args=processedfile_id))
    del db.visualizedfile[visualizedfile.id]
    redirect(URL(r=request,f='visual',args=processedfile_id))

def view_processed():
    processedfile=crud.read(db.processedfile,get(db.processedfile))
    configfile=crud.read(db.configfile,processedfile.record.configfile)
    return dict(configfile=configfile,processedfile=processedfile)

def view_visualized():
    visualizedfile=get(db.visualizedfile)
    return dict(visualizedfile=visualizedfile)

def upload_file():
    """
    Used by the web service client script to upload files.
    XMLRPCLib Binary objects proved unworkable because the entire
    file is read and held in memory before transmitting.
    """

    token_str = request.post_vars['token']
    filename = request.post_vars['uploaded_file'].filename
    process = False
    algorithm = None
    if 'process' in request.post_vars:
        process = request.post_vars['process'] == 'True'
        if 'algorithm' in request.post_vars:
            algorithms = request.post_vars['algorithm'].split(',')
    uploaded_data = request.post_vars['uploaded_file'].file
    token = db(db.uploadtoken.token == token_str).select().first()
    if token:
        file_details = detect_fileformat(uploaded_data)
    if not file_details:
        return 'Unknown file format' 
        stream = token.stream
        configfile = get_configfile_in_stream(stream, filename)

        if configfile:
            configfile.update_record(file_format=file_details['file_format'], lattice_size=file_details['lattice_size'],
                                     endianness=file_details['endianess'], precision=file_details['precision'],
                                     file=db.configfile.file.store(uploaded_data, filename))
        else:
            configfile = db.configfile.insert(format=file_details['file_format'], lattice_size=file_details['lattice_size'],
                                              endianness=file_details['endianess'], precision=file_details['precision'],
                                              stream=stream, filename=filename, posted_by=stream.posted_by,
                                              file=db.configfile.file.store(uploaded_data, filename))
            submit_job('plaquette.py',configfile,configfile.posted_by)
        if process:
            for algorithm in algorithms:
                processedfile=db.processedfile.insert(configfile=configfile,
                                                      algorithm=algorithm,
                                                      posted_by=configfile.posted_by)
                submit_job('algorihtm.py',processedfile,configfile.posted_by)
        return 'OK'
    else:
        return 'invalid token: unknown token'

def upload_script():
    f = open(request.folder + '/private/web_service_client/vis.py')
    script = f.read()
    response.headers['Content-Type'] = 'text/plain'
    response.headers['Content-Disposition'] = 'attachment; filename=upload_script.py'
    return script

def user():
    """
    exposes:
    http://..../[app]/default/user/login 
    http://..../[app]/default/user/logout
    http://..../[app]/default/user/register
    http://..../[app]/default/user/profile
    http://..../[app]/default/user/retrieve_password
    http://..../[app]/default/user/change_password
    use @auth.requires_login()
        @auth.requires_membership('group name')
        @auth.requires_permission('read','table name',record_id)
    to decorate functions that need access control
    """
    return dict(form=auth())


@auth.requires((request.args and request.args[0].endswith('.png')) or auth.user)
def download():
    """
    allows downloading of uploaded files
    http://..../[app]/default/download/[filename]
    """
    return response.download(request,db)


def call():
    """
    exposes services. for example:
    http://..../[app]/default/call/jsonrpc
    decorate with @services.jsonrpc the functions to expose
    supports xml, json, xmlrpc, jsonrpc, amfrpc, rss, csv
    """
    session.forget()
    return service()
